package data

import (
	"blog/app/travel/internal/biz"
	"blog/internal/constx"
	"blog/internal/ent"
	"blog/internal/ent/account"
	"blog/internal/ent/filesextend"
	"blog/internal/ent/predicate"
	"blog/internal/ent/tags"
	"blog/internal/ent/tagsrelation"
	"blog/internal/ent/travelextends"
	"blog/internal/ent/travels"
	"context"
	"entgo.io/ent/dialect/sql"
	"errors"
	"time"

	"github.com/go-kratos/kratos/v2/log"
)

type travelRepo struct {
	data *Data
	log  *log.Helper
}

// NewTravelRepo .
func NewTravelRepo(data *Data, logger log.Logger) biz.TravelRepo {
	return &travelRepo{
		data: data,
		log:  log.NewHelper(logger),
	}
}

func (r *travelRepo) Save(ctx context.Context, g *biz.TravelBase) (err error) {
	if len(g.Photos) == 0 {
		err = errors.New("no photos")
		return err
	}
	tx, err := r.data.db.Tx(ctx)
	if err != nil {
		return err
	}

	if g.Cover == "" {
		g.Cover = g.Photos[0]
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback()
		} else {
			err = tx.Commit()
		}
	}()
	var bulkTags []int
	for _, name := range g.Tags {
		var id int
		id, err = tx.Tags.
			Create().
			SetName(name).
			OnConflictColumns("name").DoNothing(). // 冲突时不做操作
			ID(ctx)
		if err != nil {
			return err
		}
		bulkTags = append(bulkTags, id)
	}

	var travelObj *ent.Travels
	if g.Id == 0 {
		travelObj, err = tx.Travels.Create().SetTitle(g.Title).SetCover(g.Cover).SetDescription(g.Description).SetVideo(g.Video).
			SetIsHidden(g.IsHidden).SetAccountID(int(g.AccountId)).SetPhotos(g.Photos).Save(ctx)
	} else {
		travelObj, err = tx.Travels.UpdateOneID(int(g.Id)).SetCover(g.Cover).SetTitle(g.Title).SetDescription(g.Description).SetVideo(g.Video).
			SetIsHidden(g.IsHidden).SetAccountID(int(g.AccountId)).SetPhotos(g.Photos).Save(ctx)
		if err != nil {
			return err
		}
		_, err = tx.TagsRelation.Delete().Where(tagsrelation.RelationEQ(constx.Travel)).Where(tagsrelation.RelationIDEQ(int(g.Id))).Exec(ctx)
	}
	if err != nil {
		return
	}
	var bulkTagRelations []*ent.TagsRelationCreate
	for _, tag := range bulkTags {
		one := tx.TagsRelation.Create()
		one.SetRelationID(travelObj.ID)
		one.SetRelation(constx.Travel)
		one.SetTagID(tag)
		bulkTagRelations = append(bulkTagRelations, one)
	}
	err = tx.TagsRelation.CreateBulk(bulkTagRelations...).Exec(ctx)
	if err != nil {
		return err
	}

	_, err = tx.FilesExtend.Delete().Where(filesextend.FromEQ(travels.Table),
		filesextend.FromIDEQ(int(travelObj.ID)), filesextend.FileIDNotIn(g.Photos...)).Exec(ctx)
	if err != nil {
		return
	}
	var builders []*ent.FilesExtendCreate
	for _, v := range g.Photos {
		builders = append(builders, tx.FilesExtend.Create().
			SetFileID(v).SetFrom(travels.Table).SetFromID(travelObj.ID).SetUserID(int(g.AccountId)).
			SetIsHidden(g.IsHidden).SetDeletedAt(0),
		)
	}
	err = tx.FilesExtend.CreateBulk(
		builders...,
	).OnConflictColumns(filesextend.FieldFromID, filesextend.FieldFrom, filesextend.FieldFileID).DoNothing().Exec(ctx)
	return
}

func (r *travelRepo) Delete(ctx context.Context, g *biz.TravelBase) (err error) {
	tx, err := r.data.db.Tx(ctx)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback()
		} else {
			err = tx.Commit()
		}
	}()
	_, err = tx.TagsRelation.Delete().Where(tagsrelation.RelationEQ(constx.Travel)).Where(tagsrelation.RelationIDEQ(int(g.Id))).Exec(ctx)
	if err != nil {
		return err
	}
	row, err := tx.Travels.Delete().Where(travels.IDEQ(int(g.Id)), travels.AccountIDEQ(int(g.AccountId))).Exec(ctx)
	if err != nil {
		return err
	}
	if row == 0 {
		return errors.New("travel not found")
	}
	_, err = tx.FilesExtend.Delete().Where(filesextend.FromEQ(travels.Table),
		filesextend.FromIDEQ(int(g.Id))).Exec(ctx)
	return
}
func (r *travelRepo) Thumb(ctx context.Context, g *biz.TravelDo) (err error) {
	tx, err := r.data.db.Tx(ctx)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback()
		} else {
			err = tx.Commit()
		}
	}()
	// 首先查询现有的记录
	existing, err := tx.TravelExtends.Query().
		Where(
			travelextends.AccountID(int(g.AccountId)),
			travelextends.TravelID(int(g.Id)),
		).
		Only(ctx)
	if err != nil && !ent.IsNotFound(err) {
		return err
	}
	// 如果记录已存在且状态相同，则直接返回，不需要更新
	if existing != nil && existing.IsThumb == g.Do {
		return nil
	}
	id, err := tx.TravelExtends.Create().SetAccountID(int(g.AccountId)).SetTravelID(int(g.Id)).
		SetIsThumb(g.Do).SetThumbAt(time.Now().Unix()).
		OnConflictColumns(travelextends.FieldAccountID, travelextends.FieldTravelID).
		UpdateIsThumb().ID(ctx)
	if err != nil {
		return
	}
	if id == 0 {
		return errors.New("travel extend not found")
	}
	i := 1
	if !g.Do {
		i = -1
	}
	_, err = tx.Travels.UpdateOneID(int(g.Id)).AddThumbNum(i).Save(ctx)
	return
}
func (r *travelRepo) Collect(ctx context.Context, g *biz.TravelDo) (err error) {
	tx, err := r.data.db.Tx(ctx)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback()
		} else {
			err = tx.Commit()
		}
	}()
	// 首先查询现有的记录
	existing, err := tx.TravelExtends.Query().
		Where(
			travelextends.AccountID(int(g.AccountId)),
			travelextends.TravelID(int(g.Id)),
		).
		Only(ctx)
	if err != nil && !ent.IsNotFound(err) {
		return err
	}
	// 如果记录已存在且状态相同，则直接返回，不需要更新
	if existing != nil && existing.IsCollect == g.Do {
		return nil
	}
	id, err := tx.TravelExtends.Create().SetAccountID(int(g.AccountId)).SetTravelID(int(g.Id)).
		SetIsCollect(g.Do).SetCollectAt(time.Now().Unix()).
		OnConflictColumns(travelextends.FieldAccountID, travelextends.FieldTravelID).
		UpdateIsCollect().
		ID(ctx)
	if err != nil {
		return
	}
	if id == 0 {
		return errors.New("travel extend not found")
	}
	i := 1
	if !g.Do {
		i = -1
	}
	_, err = tx.Travels.UpdateOneID(int(g.Id)).AddCollectNum(i).Save(ctx)
	return
}

func (r *travelRepo) Get(ctx context.Context, g *biz.TravelBase) (resp *biz.Travel, err error) {
	info, err := r.data.db.Travels.Query().Where(travels.IDEQ(int(g.Id))).
		Where(travels.Or(travels.AccountIDEQ(int(g.AccountId)), travels.IsHidden(false))).
		WithTagRelations(func(query *ent.TagsRelationQuery) {
			query.Where(tagsrelation.RelationEQ(constx.Travel)).
				Select(tagsrelation.FieldID, tagsrelation.FieldTagID, tagsrelation.FieldRelationID).
				WithTag()
		}).WithTravelAccount(func(query *ent.AccountQuery) {
		query.Select(account.FieldNickname, account.FieldAvatar, account.FieldDescription)
	}).First(ctx)
	if err != nil {
		return
	}
	resp = &biz.Travel{
		TravelBase: biz.TravelBase{
			Id:          info.ID,
			Title:       info.Title,
			IsHidden:    info.IsHidden,
			Description: info.Description,
			Video:       info.Video,
			Photos:      info.Photos,
			Cover:       info.Cover,
			AccountId:   info.AccountID,
			CreatedAt:   info.CreatedAt,
			UpdatedAt:   info.UpdatedAt,
		},
		TravelAccount: biz.TravelAccount{},
		TravelExtend: biz.TravelExtend{
			BrowseNum:  info.BrowseNum,
			ThumbNum:   info.ThumbNum,
			CollectNum: info.CollectNum,
		},
	}
	go r.browse(g)
	if info.Edges.TravelAccount != nil {
		resp.Nickname = info.Edges.TravelAccount.Nickname
		resp.Avatar = info.Edges.TravelAccount.Avatar
		resp.TravelAccount.Description = info.Edges.TravelAccount.Description
	}
	if len(info.Edges.TagRelations) > 0 {
		for _, v := range info.Edges.TagRelations {
			if v.Edges.Tag == nil {
				continue
			}
			resp.Tags = append(resp.Tags, v.Edges.Tag.Name)
		}
	}
	if g.AccountId == 0 {
		return
	}
	extend, err := r.data.db.TravelExtends.Query().Where(travelextends.TravelIDEQ(info.ID), travelextends.AccountIDEQ(int(g.AccountId))).Only(ctx)
	if err != nil {
		return resp, nil
	}
	resp.IsCollect = extend.IsCollect
	resp.IsThumb = extend.IsThumb
	return
}
func (r *travelRepo) browse(g *biz.TravelBase) {
	ctx := context.Background()
	tx, err := r.data.db.Tx(ctx)
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback()
		} else {
			err = tx.Commit()
		}
	}()
	err = tx.TravelExtends.Create().
		SetAccountID(int(g.AccountId)).SetTravelID(int(g.Id)).
		SetBrowseAt(time.Now().Unix()).SetBrowseNum(1).
		OnConflictColumns(travelextends.FieldAccountID, travelextends.FieldTravelID).
		AddBrowseNum(1).SetBrowseAt(time.Now().Unix()).Exec(ctx)
	if err != nil {
		return
	}
	err = r.data.db.Travels.UpdateOneID(int(g.Id)).AddBrowseNum(1).Exec(context.Background())
}

func (r *travelRepo) List(ctx context.Context, g *biz.TravelQuery) (int64, []*biz.Travel, error) {
	tx := r.data.db.Travels.Query().
		Where(travels.Or(travels.AccountIDEQ(int(g.AccountId)),
			travels.IsHidden(constx.TravelNotHidden)))
	tx = tx.WithTravelAccount(func(query *ent.AccountQuery) {
		query.Select(account.FieldNickname, account.FieldAvatar)
	})
	if len(g.Title) > 0 {
		tx.Where(travels.TitleContains(g.Title))
	}
	if g.My {
		tx = tx.Where(travels.AccountIDEQ(int(g.AccountId)))
	}

	if len(g.Keyword) > 0 {
		tx.Where(travels.Or(travels.TitleContains(g.Keyword), travels.DescriptionContains(g.Keyword)))
	}
	if len(g.Description) > 0 {
		tx.Where(travels.DescriptionContains(g.Description))
	}
	if len(g.Sort) > 0 {
		tx = tx.Order(ent.Desc(g.Sort))
	}
	if len(g.Tags) > 0 {
		conditions := make([]predicate.Travels, 0, len(g.Tags))
		for _, tag := range g.Tags {
			conditions = append(conditions, travels.HasTagRelationsWith(
				tagsrelation.RelationEQ(constx.Travel),
				tagsrelation.HasTagWith(
					tags.NameEQ(tag),
				),
			))
		}
		tx = tx.Where(conditions...)
		//tx = tx.Where(travels.HasTagRelationsWith(
		//	tagsrelation.RelationEQ(constx.Travel),
		//	tagsrelation.HasTagWith(
		//		tags.NameIn(g.Tags...),
		//	),
		//))
	}
	if g.AccountId > 0 {
		tx = tx.WithTravelExtends(func(query *ent.TravelExtendsQuery) {
			query = query.Select(travelextends.FieldIsThumb, travelextends.FieldIsCollect).Where(travelextends.AccountIDEQ(int(g.AccountId)))
			if g.MyThumb {
				query = query.Where(travelextends.IsThumbEQ(true))
			}
			if g.MyCollect {
				query = query.Where(travelextends.IsCollectEQ(true))
			}
		})
	}
	total, err := tx.Count(ctx)
	if err != nil {
		return 0, nil, err
	}
	if total == 0 {
		return 0, []*biz.Travel{}, nil
	}
	tx = tx.Order(ent.Desc(travels.FieldID))
	resp := make([]*biz.Travel, 0, g.GetPageSize())
	// account.FieldNickname, account.FieldAvatar,
	res, err := tx.Offset(int(g.GetOffset())).Limit(int(g.GetPageSize())).
		WithTagRelations(func(query *ent.TagsRelationQuery) {
			query.Where(tagsrelation.RelationEQ(constx.Travel)).
				Select(tagsrelation.FieldID, tagsrelation.FieldTagID, tagsrelation.FieldRelationID).
				WithTag()
		}).
		All(ctx) //.Scan(ctx, &resp)
	for _, info := range res {
		one := &biz.Travel{
			TravelBase: biz.TravelBase{
				Id:          info.ID,
				Title:       info.Title,
				IsHidden:    info.IsHidden,
				Description: info.Description,
				Video:       info.Video,
				Cover:       info.Cover,
				Photos:      info.Photos,
				AccountId:   info.AccountID,
				CreatedAt:   info.CreatedAt,
				UpdatedAt:   info.UpdatedAt,
			},
			TravelExtend: biz.TravelExtend{
				BrowseNum:  info.BrowseNum,
				ThumbNum:   info.ThumbNum,
				CollectNum: info.CollectNum,
			},
		}
		if one.Cover == "" && len(info.Photos) > 0 {
			one.Cover = info.Photos[0]
		}
		if info.Edges.TravelAccount != nil {
			one.TravelAccount.Nickname = info.Edges.TravelAccount.Nickname
			one.TravelAccount.Avatar = info.Edges.TravelAccount.Avatar
		}
		if info.Edges.TravelExtends != nil && len(info.Edges.TravelExtends) == 1 {
			item := info.Edges.TravelExtends[0]
			one.TravelExtend.IsThumb = item.IsThumb
			one.TravelExtend.IsCollect = item.IsCollect
		}
		if len(info.Edges.TagRelations) > 0 {
			for _, v := range info.Edges.TagRelations {
				if v.Edges.Tag == nil {
					continue
				}
				one.Tags = append(one.Tags, v.Edges.Tag.Name)
			}
		}
		resp = append(resp, one)
	}
	return int64(total), resp, err
}

func (r *travelRepo) Tags(ctx context.Context, query *biz.TravelQuery) (int64, []*biz.TravelTags, error) {
	tx := r.data.db.TagsRelation.Query().Where(tagsrelation.RelationEQ(constx.Travel))
	var scopes []predicate.Travels
	scopes = append(scopes, travels.Or(
		travels.AccountIDEQ(int(query.AccountId)),
		travels.IsHiddenEQ(constx.TravelNotHidden),
	))
	if query.Title != "" {
		scopes = append(scopes, travels.TitleContains(query.Title))
	}
	if query.My {
		scopes = append(scopes, travels.AccountIDEQ(int(query.AccountId)))
	}

	if len(query.Keyword) > 0 {
		scopes = append(scopes, travels.Or(travels.TitleContains(query.Keyword),
			travels.DescriptionContains(query.Keyword)))
	}
	if len(query.Description) > 0 {
		scopes = append(scopes, travels.DescriptionContains(query.Description))
	}
	tx = tx.Where(tagsrelation.HasTravelWith(scopes...))
	if len(query.Tags) > 0 {
		conditions := make([]predicate.TagsRelation, 0, len(query.Tags))
		for _, tag := range query.Tags {
			conditions = append(conditions, tagsrelation.HasTravelWith(travels.HasTagRelationsWith(
				tagsrelation.RelationEQ(constx.Travel),
				tagsrelation.HasTagWith(
					tags.NameEQ(tag),
				),
			)))
		}
		tx = tx.Where(conditions...)
		//tx = tx.Where(tagsrelation.HasTravelWith(travels.HasTagRelationsWith(
		//	tagsrelation.RelationEQ(constx.Travel),
		//	tagsrelation.HasTagWith(
		//		tags.NameIn(query.Tags...),
		//	),
		//)))
	}
	if query.AccountId > 0 && (query.MyThumb || query.MyCollect) {
		tx = tx.WithTravel(func(queryx *ent.TravelsQuery) {
			queryx.WithTravelExtends(func(queryz *ent.TravelExtendsQuery) {
				queryz = queryz.Select(travelextends.FieldIsThumb, travelextends.FieldIsCollect).Where(travelextends.AccountIDEQ(int(query.AccountId)))
				if query.MyThumb {
					queryz = queryz.Where(travelextends.IsThumbEQ(true))
				}
				if query.MyCollect {
					queryz = queryz.Where(travelextends.IsCollectEQ(true))
				}
			})
		})
	}
	total, err := tx.Clone().Select(tagsrelation.FieldTagID).
		Unique(true).Count(ctx)
	if err != nil {
		return 0, nil, err
	}
	var aggrates []ent.AggregateFunc
	//aggrates = append(aggrates, func(selector *sql.Selector) string {
	//	return ""
	//})
	//aggrates = append(aggrates, func(selector *sql.Selector) string {
	//	return ""
	//})
	aggrates = append(aggrates, func(s *sql.Selector) string {
		t := sql.Table(tags.Table)
		return s.Join(t).On(t.C(tags.FieldID), s.C(tagsrelation.FieldTagID)).
			Select(tagsrelation.FieldTagID, tags.FieldName, "count(*) as count").String()
	})
	resp := make([]*biz.TravelTags, 0, query.PageSize)
	err = tx.Order(func(selector *sql.Selector) {
		selector.OrderBy("count(*) desc,tag_id desc")
	}).Limit(int(query.PageSize)).
		GroupBy(tagsrelation.FieldTagID).Aggregate(aggrates...).Scan(ctx, &resp)
	return int64(total), resp, err
}
